﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ReadOnlyObservableCollection.cs" company="XamlNinja">
//   2011 Richard Griffin and Ollie Riches
// </copyright>
// <summary>
//   The read only observable collection.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace WP7Contrib.Collections
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using Common;

    /// <summary>
    /// Read only observable collection.
    /// </summary>
    /// <typeparam name="T">The type being used for binding.
    /// </typeparam>
    public sealed class ReadOnlyObservableCollection<T> : BaseCollectionModel, IObservableCollection<T>
    {
        /// <summary>
        /// The internal observable collection.
        /// </summary>
        private readonly IObservableCollection<T> observableCollection;

        /// <summary>
        /// Initializes a new instance of the <see cref="ReadOnlyObservableCollection{T}"/> class.
        /// </summary>
        /// <param name="observableCollection">
        /// The observable collection we wish to make a read only version of.
        /// </param>
        /// <exception cref="ArgumentNullException">Throws an argument null exception if the observable collection is null.
        /// </exception>
        public ReadOnlyObservableCollection(IObservableCollection<T> observableCollection)
        {
            if (observableCollection == null)
            {
                throw new ArgumentNullException("observableCollection", "Observable collection is null!");
            }

            this.observableCollection = observableCollection;
            this.observableCollection.CollectionChanged += this.HandleCollectionChanged;
        }

        public ReadOnlyObservableCollection(IEnumerable<T> collection)
        {
            if (collection == null)
            {
                throw new ArgumentNullException("collection", "Collection is null!");
            }

            this.observableCollection = new ObservableCollection<T>(collection);
            this.observableCollection.CollectionChanged += this.HandleCollectionChanged;
        }

        #region IObservableCollection<T> Members

        /// <summary>
        /// Throws a NotSupportedException exception.
        /// </summary>
        /// <param name="items">
        /// The items to be added to the collection.
        /// </param>
        /// <exception cref="NotSupportedException">
        /// </exception>
        public void AddRange(IEnumerable<T> items)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// Returns an enumerator for the collection.
        /// </returns>
        public IEnumerator<T> GetEnumerator()
        {
            return this.observableCollection.GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// Returns an enumerator for the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.observableCollection.GetEnumerator();
        }

        /// <summary>
        /// Throws a NotSupportedException exception.
        /// </summary>
        /// <param name="item">
        /// The item to be added to the collection.
        /// </param>
        /// <exception cref="NotSupportedException">
        /// </exception>
        public void Add(T item)
        {
            throw new NotSupportedException();
        }

        int IList.Add(object value)
        {
            throw new NotSupportedException();
        }

        bool IList.Contains(object value)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Throws a NotSupportedException exception.
        /// </summary>
        /// <exception cref="NotSupportedException">
        /// </exception>
        public void Clear()
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Determines if the collection contains an item.
        /// </summary>
        /// <param name="item">
        /// The item to locate in the collection. 
        /// </param>
        /// <returns>
        /// Returns true if the collection contains the item.
        /// </returns>
        public bool Contains(T item)
        {
            return this.observableCollection.Contains(item);
        }

        /// <summary>
        /// Copy the collection to an array.
        /// </summary>
        /// <param name="array">
        /// The one-dimensional Array that is the destination of the items copied from IObservableCollection. The Array must have zero-based indexing. 
        /// </param>
        /// <param name="arrayIndex">
        /// The zero-based index in array at which copying begins. 
        /// </param>
        public void CopyTo(T[] array, int arrayIndex)
        {
            this.observableCollection.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Copy the collection to an array.
        /// </summary>
        /// <param name="array">
        /// The one-dimensional Array that is the destination of the items copied from IObservableCollection. The Array must have zero-based indexing. 
        /// </param>
        /// <param name="arrayIndex">
        /// The zero-based index in array at which copying begins. 
        /// </param>
        void ICollection.CopyTo(Array array, int arrayIndex)
        {
            this.observableCollection.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Throws a NotSupportedException exception.
        /// </summary>
        /// <param name="item">
        /// The item to be removed from the collection
        /// </param>
        /// <returns>
        /// Returns the result of removing an item from the collection, if the item existed it returns true else false.
        /// </returns>
        /// <exception cref="NotSupportedException">
        /// </exception>
        public bool Remove(T item)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Throws a NotSupportedException exception.
        /// </summary>
        /// <param name="item">
        /// The item to be removed from the collection
        /// </param>
        /// <returns>
        /// Returns the result of removing an item from the collection, if the item existed it returns true else false.
        /// </returns>
        /// <exception cref="NotSupportedException">
        /// </exception>
        void IList.Remove(object item)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Gets the number of items in the collection.
        /// </summary>
        public int Count
        {
            get { return ((IList<T>)this.observableCollection).Count; }
        }

        /// <summary>
        /// Gets an object that can be used to synchronize access to the ICollection. (Inherited from ICollection.)
        /// </summary>
        object ICollection.SyncRoot
        {
            get { return this.observableCollection.SyncRoot; }
        }

        /// <summary>
        /// Gets a value indicating whether access to the ICollection is synchronized (thread safe). (Inherited from ICollection.)
        /// </summary>
        bool ICollection.IsSynchronized
        {
            get { return this.observableCollection.IsSynchronized; }
        }

        /// <summary>
        /// Gets a value indicating whether is read only, return true.
        /// </summary>
        public bool IsReadOnly
        {
            get { return true; }
        }

        /// <summary>
        /// Gets a value indicating whether the IList has a fixed size.
        /// </summary>
        bool IList.IsFixedSize
        {
            get { return true; }
        }

        /// <summary>
        /// Returns the index of an item in the collection.
        /// </summary>
        /// <param name="item">
        /// The item to locate in the collection. 
        /// </param>
        /// <returns>
        /// The index of item if found in the collection; otherwise, -1.
        /// </returns>
        public int IndexOf(T item)
        {
            return this.observableCollection.IndexOf(item);
        }

        /// <summary>
        /// Returns the index of an item in the collection.
        /// </summary>
        /// <param name="item">
        /// The item to locate in the collection. 
        /// </param>
        /// <returns>
        /// The index of item if found in the collection; otherwise, -1.
        /// </returns>
        int IList.IndexOf(object item)
        {
            return this.observableCollection.IndexOf((T)item);
        }

        /// <summary>
        /// Throws a NotSupportedException exception.
        /// </summary>
        /// <param name="index">
        /// The index to insert the item at in the collection.
        /// </param>
        /// <param name="item">
        /// The item to be inserted into the collection.
        /// </param>
        public void Insert(int index, T item)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Throws a NotSupportedException exception.
        /// </summary>
        /// <param name="index">
        /// The index to insert the item at in the collection.
        /// </param>
        /// <param name="item">
        /// The item to be inserted into the collection.
        /// </param>
        void IList.Insert(int index, object item)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Throws a NotSupportedException exception.
        /// </summary>
        /// <param name="index">
        /// The index of the item to be removed.
        /// </param>
        public void RemoveAt(int index)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Gets or the item at the specified index, the set method throws a NotSupportedException exception.
        /// </summary>
        /// <param name="index">
        /// The zero-based index of the element to get or set. 
        /// </param>
        object IList.this[int index]
        {
            get { return ((IList<T>)this.observableCollection)[index]; }
            set { throw new NotSupportedException(); }
        }

        /// <summary>
        /// Gets or the item at the specified index, the set method throws a NotSupportedException exception.
        /// </summary>
        /// <param name="index">
        /// The zero-based index of the element to get or set. 
        /// </param>
        public T this[int index]
        {
            get { return ((IList<T>)this.observableCollection)[index]; }
            set { throw new NotSupportedException(); }
        }

        #endregion

        /// <summary>
        /// Handles the internal collection changed.
        /// </summary>
        /// <param name="sender">
        /// The sender of the event
        /// </param>
        /// <param name="args">
        /// The arguments for the event.
        /// </param>
        private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
        {
            if (args.Action == NotifyCollectionChangedAction.Reset)
            {
                this.RaiseCollectionChanged(args.Action);
            }
            else if (args.Action == NotifyCollectionChangedAction.Add)
            {
                var index = args.NewStartingIndex;
                foreach (var item in args.NewItems)
                {
                    this.RaiseCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
                    index++;
                }
            }
            else if (args.Action == NotifyCollectionChangedAction.Remove)
            {
                var index = args.OldStartingIndex;
                foreach (var item in args.OldItems)
                {
                    this.RaiseCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
                    index++;
                }
            }
            else
            {
                this.RaiseCollectionChanged(args.Action, args.NewItems, args.OldItems, args.NewStartingIndex);
            }
        }
    }
}